// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
// SPDX-License-Identifier: MIT

package errors

import (
	"context"
	"fmt"
	"strings"
	"testing"
	"time"
)

func TestStructuredError_Error(t *testing.T) {
	tests := []struct {
		name     string
		err      *StructuredError
		expected string
	}{
		{
			name: "error with details",
			err: &StructuredError{
				Type:    ErrorTypeConnection,
				Code:    "CONN_FAILED",
				Message: "Connection failed",
				Details: "socket not found",
			},
			expected: "[connection:CONN_FAILED] Connection failed: socket not found",
		},
		{
			name: "error without details",
			err: &StructuredError{
				Type:    ErrorTypeConfig,
				Code:    "CONFIG_INVALID",
				Message: "Invalid configuration",
			},
			expected: "[config:CONFIG_INVALID] Invalid configuration",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := tt.err.Error(); got != tt.expected {
				t.Errorf("StructuredError.Error() = %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestStructuredError_WithContext(t *testing.T) {
	err := New(ErrorTypeConnection, "TEST", "test error")
	err.WithContext("socket", "/tmp/test.sock")
	err.WithContext("timeout", "30s")

	if err.Context["socket"] != "/tmp/test.sock" {
		t.Errorf("Expected socket context to be '/tmp/test.sock', got %v", err.Context["socket"])
	}

	if err.Context["timeout"] != "30s" {
		t.Errorf("Expected timeout context to be '30s', got %v", err.Context["timeout"])
	}
}

func TestStructuredError_WithOperation(t *testing.T) {
	err := New(ErrorTypeConnection, "TEST", "test error")
	err.WithOperation("connect")

	if err.Operation != "connect" {
		t.Errorf("Expected operation to be 'connect', got %v", err.Operation)
	}
}

func TestStructuredError_WithStackTrace(t *testing.T) {
	err := New(ErrorTypeSystem, "TEST", "test error")
	err.WithStackTrace()

	if err.StackTrace == "" {
		t.Error("Expected stack trace to be set")
	}

	if !strings.Contains(err.StackTrace, "TestStructuredError_WithStackTrace") {
		t.Error("Expected stack trace to contain test function name")
	}
}

func TestNew(t *testing.T) {
	err := New(ErrorTypeConnection, "CONN_FAILED", "Connection failed")

	if err.Type != ErrorTypeConnection {
		t.Errorf("Expected type to be %v, got %v", ErrorTypeConnection, err.Type)
	}

	if err.Code != "CONN_FAILED" {
		t.Errorf("Expected code to be 'CONN_FAILED', got %v", err.Code)
	}

	if err.Message != "Connection failed" {
		t.Errorf("Expected message to be 'Connection failed', got %v", err.Message)
	}

	if err.Severity != SeverityMedium {
		t.Errorf("Expected default severity to be %v, got %v", SeverityMedium, err.Severity)
	}

	if err.Retryable {
		t.Error("Expected default retryable to be false")
	}
}

func TestWrap(t *testing.T) {
	originalErr := fmt.Errorf("original error")
	wrappedErr := Wrap(originalErr, ErrorTypeParser, "PARSE_FAILED", "Failed to parse")

	if wrappedErr.Cause != originalErr {
		t.Errorf("Expected cause to be original error, got %v", wrappedErr.Cause)
	}

	if wrappedErr.Unwrap() != originalErr {
		t.Errorf("Expected Unwrap() to return original error, got %v", wrappedErr.Unwrap())
	}
}

func TestNewConnectionError(t *testing.T) {
	originalErr := fmt.Errorf("connection refused")
	err := NewConnectionError("/tmp/test.sock", "connect", originalErr)

	if err.Type != ErrorTypeConnection {
		t.Errorf("Expected type to be %v, got %v", ErrorTypeConnection, err.Type)
	}

	if err.Code != "CONN_FAILED" {
		t.Errorf("Expected code to be 'CONN_FAILED', got %v", err.Code)
	}

	if err.Context["socket"] != "/tmp/test.sock" {
		t.Errorf("Expected socket context to be '/tmp/test.sock', got %v", err.Context["socket"])
	}

	if err.Operation != "connect" {
		t.Errorf("Expected operation to be 'connect', got %v", err.Operation)
	}

	if err.Severity != SeverityHigh {
		t.Errorf("Expected severity to be %v, got %v", SeverityHigh, err.Severity)
	}

	if !err.Retryable {
		t.Error("Expected connection refused error to be retryable")
	}
}

func TestNewTimeoutError(t *testing.T) {
	timeout := 30 * time.Second
	originalErr := context.DeadlineExceeded
	err := NewTimeoutError("fetch_stats", timeout, originalErr)

	if err.Type != ErrorTypeTimeout {
		t.Errorf("Expected type to be %v, got %v", ErrorTypeTimeout, err.Type)
	}

	if err.Code != "TIMEOUT" {
		t.Errorf("Expected code to be 'TIMEOUT', got %v", err.Code)
	}

	if err.Operation != "fetch_stats" {
		t.Errorf("Expected operation to be 'fetch_stats', got %v", err.Operation)
	}

	if err.Context["timeout"] != timeout.String() {
		t.Errorf("Expected timeout context to be %v, got %v", timeout.String(), err.Context["timeout"])
	}

	if !err.Retryable {
		t.Error("Expected timeout error to be retryable")
	}
}

func TestNewConfigError(t *testing.T) {
	err := NewConfigError("port", "99999", "port out of range")

	if err.Type != ErrorTypeConfig {
		t.Errorf("Expected type to be %v, got %v", ErrorTypeConfig, err.Type)
	}

	if err.Code != "CONFIG_INVALID" {
		t.Errorf("Expected code to be 'CONFIG_INVALID', got %v", err.Code)
	}

	if err.Context["field"] != "port" {
		t.Errorf("Expected field context to be 'port', got %v", err.Context["field"])
	}

	if err.Context["value"] != "99999" {
		t.Errorf("Expected value context to be '99999', got %v", err.Context["value"])
	}

	if err.Details != "port out of range" {
		t.Errorf("Expected details to be 'port out of range', got %v", err.Details)
	}

	if err.Severity != SeverityCritical {
		t.Errorf("Expected severity to be %v, got %v", SeverityCritical, err.Severity)
	}

	if err.Retryable {
		t.Error("Expected config error to not be retryable")
	}
}

func TestNewParserError(t *testing.T) {
	originalErr := fmt.Errorf("invalid JSON")
	data := `{"invalid": json}`
	err := NewParserError("JSON", data, originalErr)

	if err.Type != ErrorTypeParser {
		t.Errorf("Expected type to be %v, got %v", ErrorTypeParser, err.Type)
	}

	if err.Context["format"] != "JSON" {
		t.Errorf("Expected format context to be 'JSON', got %v", err.Context["format"])
	}

	if err.Context["data_preview"] != data {
		t.Errorf("Expected data_preview context to be %v, got %v", data, err.Context["data_preview"])
	}
}

func TestNewValidationError(t *testing.T) {
	err := NewValidationError("timeout", "0s", "must be positive")

	if err.Type != ErrorTypeValidation {
		t.Errorf("Expected type to be %v, got %v", ErrorTypeValidation, err.Type)
	}

	if err.Context["field"] != "timeout" {
		t.Errorf("Expected field context to be 'timeout', got %v", err.Context["field"])
	}

	if err.Context["constraint"] != "must be positive" {
		t.Errorf("Expected constraint context to be 'must be positive', got %v", err.Context["constraint"])
	}
}

func TestIsConnectionError(t *testing.T) {
	connErr := NewConnectionError("/tmp/test.sock", "connect", fmt.Errorf("connection refused"))
	otherErr := NewConfigError("port", "invalid", "not a number")

	if !IsConnectionError(connErr) {
		t.Error("Expected connection error to be identified as connection error")
	}

	if IsConnectionError(otherErr) {
		t.Error("Expected config error to not be identified as connection error")
	}

	if IsConnectionError(nil) {
		t.Error("Expected nil error to not be identified as connection error")
	}
}

func TestIsTimeoutError(t *testing.T) {
	// 测试结构化超时错误
	timeoutErr := NewTimeoutError("test", 30*time.Second, context.DeadlineExceeded)
	if !IsTimeoutError(timeoutErr) {
		t.Error("Expected structured timeout error to be identified as timeout error")
	}

	// 测试上下文超时错误
	if !IsTimeoutError(context.DeadlineExceeded) {
		t.Error("Expected context.DeadlineExceeded to be identified as timeout error")
	}

	// 测试非超时错误
	connErr := NewConnectionError("/tmp/test.sock", "connect", fmt.Errorf("connection refused"))
	if IsTimeoutError(connErr) {
		t.Error("Expected connection error to not be identified as timeout error")
	}

	if IsTimeoutError(nil) {
		t.Error("Expected nil error to not be identified as timeout error")
	}
}

func TestIsRetryable(t *testing.T) {
	// 可重试的连接错误
	retryableErr := NewConnectionError("/tmp/test.sock", "connect", fmt.Errorf("connection refused"))
	if !IsRetryable(retryableErr) {
		t.Error("Expected connection refused error to be retryable")
	}

	// 不可重试的配置错误
	nonRetryableErr := NewConfigError("port", "invalid", "not a number")
	if IsRetryable(nonRetryableErr) {
		t.Error("Expected config error to not be retryable")
	}

	// 测试非结构化错误的启发式判断
	if !IsRetryable(fmt.Errorf("connection refused")) {
		t.Error("Expected 'connection refused' error to be retryable")
	}

	if IsRetryable(fmt.Errorf("invalid syntax")) {
		t.Error("Expected 'invalid syntax' error to not be retryable")
	}
}

func TestGetSeverity(t *testing.T) {
	criticalErr := NewConfigError("port", "invalid", "not a number")
	if GetSeverity(criticalErr) != SeverityCritical {
		t.Errorf("Expected critical severity, got %v", GetSeverity(criticalErr))
	}

	// 非结构化错误应该返回默认严重程度
	if GetSeverity(fmt.Errorf("some error")) != SeverityMedium {
		t.Errorf("Expected medium severity for non-structured error, got %v", GetSeverity(fmt.Errorf("some error")))
	}
}

func TestIsRetryableConnectionError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{"connection refused", fmt.Errorf("connection refused"), true},
		{"network unreachable", fmt.Errorf("network is unreachable"), true},
		{"timeout", fmt.Errorf("timeout occurred"), true},
		{"temporary failure", fmt.Errorf("temporary failure in name resolution"), true},
		{"invalid syntax", fmt.Errorf("invalid syntax"), false},
		{"permission denied", fmt.Errorf("permission denied"), false},
		{"nil error", nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := isRetryableConnectionError(tt.err); got != tt.expected {
				t.Errorf("isRetryableConnectionError() = %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestIsRetryableSystemError(t *testing.T) {
	tests := []struct {
		name     string
		err      error
		expected bool
	}{
		{"too many open files", fmt.Errorf("too many open files"), true},
		{"resource unavailable", fmt.Errorf("resource temporarily unavailable"), true},
		{"no space left", fmt.Errorf("no space left on device"), true},
		{"permission denied", fmt.Errorf("permission denied"), false},
		{"file not found", fmt.Errorf("file not found"), false},
		{"nil error", nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := isRetryableSystemError(tt.err); got != tt.expected {
				t.Errorf("isRetryableSystemError() = %v, want %v", got, tt.expected)
			}
		})
	}
}

func TestTruncateString(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		maxLen   int
		expected string
	}{
		{"short string", "hello", 10, "hello"},
		{"exact length", "hello", 5, "hello"},
		{"long string", "hello world", 5, "hello..."},
		{"empty string", "", 5, ""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := truncateString(tt.input, tt.maxLen); got != tt.expected {
				t.Errorf("truncateString() = %v, want %v", got, tt.expected)
			}
		})
	}
}

// Mock logger for testing
type mockLogger struct {
	lastMessage string
	lastLevel   string
	fields      map[string]interface{}
}

func (m *mockLogger) WithError(err error) interface {
	Error(string)
	Warn(string)
	Info(string)
} {
	return &mockLoggerEntry{logger: m, fields: make(map[string]interface{})}
}

func (m *mockLogger) WithField(key string, value interface{}) interface {
	Error(string)
	Warn(string)
	Info(string)
} {
	entry := &mockLoggerEntry{logger: m, fields: make(map[string]interface{})}
	entry.fields[key] = value
	return entry
}

type mockLoggerEntry struct {
	logger *mockLogger
	fields map[string]interface{}
}

func (m *mockLoggerEntry) Error(msg string) {
	m.logger.lastMessage = msg
	m.logger.lastLevel = "error"
	m.logger.fields = m.fields
}

func (m *mockLoggerEntry) Warn(msg string) {
	m.logger.lastMessage = msg
	m.logger.lastLevel = "warn"
	m.logger.fields = m.fields
}

func (m *mockLoggerEntry) Info(msg string) {
	m.logger.lastMessage = msg
	m.logger.lastLevel = "info"
	m.logger.fields = m.fields
}

func TestDefaultErrorHandler_HandleError(t *testing.T) {
	logger := &mockLogger{}
	handler := &DefaultErrorHandler{Logger: logger}

	// 测试结构化错误处理
	err := NewConnectionError("/tmp/test.sock", "connect", fmt.Errorf("connection refused"))
	err.Severity = SeverityCritical

	handledErr := handler.HandleError(err, "test_component")

	if handledErr != err {
		t.Error("Expected handler to return the same error")
	}

	if logger.lastLevel != "error" {
		t.Errorf("Expected log level to be 'error', got %v", logger.lastLevel)
	}

	if logger.lastMessage != "Connection failed" {
		t.Errorf("Expected log message to be 'Connection failed', got %v", logger.lastMessage)
	}

	// 注意：由于简化的接口实现，字段可能不会被正确传递
	// 这里我们只检查基本的错误处理功能
	if logger.fields["component"] != "test_component" {
		t.Errorf("Expected component field to be 'test_component', got %v", logger.fields["component"])
	}

	// 测试nil错误
	if handler.HandleError(nil, "test") != nil {
		t.Error("Expected handler to return nil for nil error")
	}
}
